/**
 *    Copyright 2009-2021 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.executor;

import static org.apache.ibatis.executor.ExecutionPlaceholder.EXECUTION_PLACEHOLDER;

import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.cache.impl.PerpetualCache;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.statement.StatementUtil;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.logging.jdbc.ConnectionLogger;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.mapping.StatementType;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.LocalCacheScope;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.transaction.Transaction;
import org.apache.ibatis.type.TypeHandlerRegistry;

/**
 * executor的抽象类
 * <pre>
 *     主要提供了缓存管理和事务管理的基本功能.
 *     继承{@link BaseExecutor}的子类只要实现四个基本方法来晚上数据库相关操作即可。
 * </pre>
 * @author Clinton Begin
 */
public abstract class BaseExecutor implements Executor {

  private static final Log log = LogFactory.getLog(BaseExecutor.class);

  /** 实现事务的提交，回滚和关闭操作 */
  protected Transaction transaction;
  /** 其中封装的 Executor 对象 */
  protected Executor wrapper;
  /** 延迟加载队列 */
  protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
  /**
   * 一级缓存
   * <pre>
   *     用于缓存该 Executor 对象查询结果集映射得到的结果对象。
   * </pre>
   */
  protected PerpetualCache localCache;
  /**
   * 一级缓存
   * <pre>
   *     用于缓存输出类型的参数
   * </pre>
   */
  protected PerpetualCache localOutputParameterCache;
  protected Configuration configuration;

  /** 查询层次 */
  protected int queryStack;
  /** executor 是否已关闭 */
  private boolean closed;

  protected BaseExecutor(Configuration configuration, Transaction transaction) {
    this.transaction = transaction;
    this.deferredLoads = new ConcurrentLinkedQueue<>();
    this.localCache = new PerpetualCache("LocalCache");
    this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
    this.closed = false;
    this.configuration = configuration;
    this.wrapper = this;
  }

  @Override
  public Transaction getTransaction() {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    return transaction;
  }

  @Override
  public void close(boolean forceRollback) {
    try {
      try {
        rollback(forceRollback);
      } finally {
        if (transaction != null) {
          transaction.close();
        }
      }
    } catch (SQLException e) {
      // Ignore.  There's nothing that can be done at this point.
      log.warn("Unexpected exception on closing transaction.  Cause: " + e);
    } finally {
      transaction = null;
      deferredLoads = null;
      localCache = null;
      localOutputParameterCache = null;
      closed = true;
    }
  }

  /**
   * 检测是否缓存了指定查询的结果对象
   */
  @Override
  public boolean isClosed() {
    return closed;
  }

  @Override
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    // 检测当前 Executor是否已经关闭
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    // 清空一级缓存
    clearLocalCache();
    // 调用 doUpdate()方法执行 SQL 语句
    return doUpdate(ms, parameter);
  }

  /**
   * 主要针对批处理多条 SQL 语句的
   * <pre>
   *     该方法会调用 doFlushStatements() 这个基本方法处理 Executor 中缓存的多条 SQL 语句。
   *     在 BaseExecutor.commit()、rollback()等方法中都会首先调用 flushStatements()方法
   * </pre>
   */
  @Override
  public List<BatchResult> flushStatements() throws SQLException {
    return flushStatements(false);
  }

  /**
   * 处理多条 SQL 语句的
   * @param isRollBack 表示是否执行 Executor 中缓存的 SQL 语句。false:执行；true:不执行
   */
  public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
    // 检测当前 Executor是否已经关闭
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    // 调用 doFlushStatements()这个基本方法，其参数isRollBack 表示是否执行 Executor 中缓存的 SQL 语句
    // false:执行；true:不执行
    return doFlushStatements(isRollBack);
  }

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 获取 BoundSql 对象
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 创建 CacheKey 对象
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    // 调用 下一个 query() 的另一个重载
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

  @SuppressWarnings("unchecked")
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    // 检测当前 Executor是否已经关闭
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      // 非嵌套查询，并且<select>节点配置的 flushCache 属性为 true 时，才情况一级缓存。
      // flushCache 配置项影响一级缓存中结果对象存活时长的第一方面
      clearLocalCache();
    }
    List<E> list;
    try {
      // 增加查询层次
      queryStack++;
      // 从一级缓存中获取数据
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        // 针对存储过程调用的处理，其功能是：在一级缓存命中时，获取缓存中保持的输出类型参数，
        // 并设置到用户传入的实参（paramter)对象中
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
    	// 如果一级缓存没有数据，则从数据库查询数据
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      // 减少查询层次
      queryStack--;
    }
    if (queryStack == 0) {
      // 在最外层的查询结束时，所有嵌套查询也已经完成，相关缓存项也已经完全加载，
      // 所有在这里可以触发 DeferredLoad 加载一级缓存中记录的嵌套查询的结果对象
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      // 加载完成后，清空 deferredLoads 集合
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // 根据 localCacheScpe 配置决定是否清空一级缓存，
        // localCacheScope配置是影响一级缓存中结果对象存活的第二个方面
        clearLocalCache();
      }
    }
    return list;
  }

  /**
   * 查询数据库
   * <pre>
   *     queryCursor()和query()方法类型，但它不会直接将结果集映射为结果对象，
   *     而是将结果封装成Cursor对想并返回。待用户遍历Cursor时才真正完成结果集的映射操作
   * </pre>
   */
  @Override
  public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    return doQueryCursor(ms, parameter, rowBounds, boundSql);
  }

  /**
   * 负责创建 DeferredLoad 对象并将其添加到 deferredLoads 机会中
   */
  @Override
  public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
    // 检测当前 Executor是否已经关闭
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    // 创建 DeferredLoad 对象
    DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType);
    if (deferredLoad.canLoad()) {
      // 一级缓存中已经记录了指定查询的结果对象，直接从缓存中加载对象，并设置到外出对象中
      deferredLoad.load();
    } else {
      deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType));
    }
  }

  @Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    // 检测当前 Executor是否已经关闭
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    // 创建 CacheKey 对象
    CacheKey cacheKey = new CacheKey();
    // 将 MappedStatement 的 id 添加到 CacheKey 对象中
    cacheKey.update(ms.getId());
    // 将 offset 添加到 CacheKey 对象中
    cacheKey.update(rowBounds.getOffset());
    // 将 limit 添加到 CacheKey 对象中
    cacheKey.update(rowBounds.getLimit());
    // 将 SQL 语句添加到 CacheKey 对象中
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // 获取用户传入的实参，并添加到 CacheKey 对象中
    for (ParameterMapping parameterMapping : parameterMappings) {
      // 过滤调输出类型的参数
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        // 将实参添加到 CacheKey 对象中
        cacheKey.update(value);
      }
    }
    if (configuration.getEnvironment() != null) {
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

  @Override
  public boolean isCached(MappedStatement ms, CacheKey key) {
    // 检测缓存中是否缓存了 CacheKey 对应的对象
    return localCache.getObject(key) != null;
  }

  @Override
  public void commit(boolean required) throws SQLException {
    // 检测当前 Executor是否已经关闭
    if (closed) {
      throw new ExecutorException("Cannot commit, transaction is already closed");
    }
    // 清空一级缓存
    clearLocalCache();
    // 执行缓存的 Sql 语句，其中调用了 flushStatements(false) 方法
    flushStatements();
    // 根据 required 参数决定是否提交事务
    if (required) {
      transaction.commit();
    }
  }

  @Override
  public void rollback(boolean required) throws SQLException {
    // 检测当前 Executor是否已经关闭
    if (!closed) {
      try {
        // 清空一级缓存
        clearLocalCache();
        // 不执行缓存的 SQL语句，其中调用 flushStatements(true) 方法
        flushStatements(true);
      } finally {
        // 根据 required 参数决定是否回滚事务
        if (required) {
          transaction.rollback();
        }
      }
    }
  }

  @Override
  public void clearLocalCache() {
    if (!closed) {
      localCache.clear();
      localOutputParameterCache.clear();
    }
  }

  protected abstract int doUpdate(MappedStatement ms, Object parameter)
      throws SQLException;

  /**
   * 主要针对批处理多条 SQL 语句的
   * @param isRollback 是否执行 Executor 中缓存的 SQL 语句。false:执行；true:不执行
   */
  protected abstract List<BatchResult> doFlushStatements(boolean isRollback)
      throws SQLException;

  protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException;

  protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
      throws SQLException;

  protected void closeStatement(Statement statement) {
    if (statement != null) {
      try {
        statement.close();
      } catch (SQLException e) {
        // ignore
      }
    }
  }

  /**
   * Apply a transaction timeout.
   * <br/>
   * 事务的超时
   * @param statement a current statement
   * @throws SQLException if a database access error occurs, this method is called on a closed <code>Statement</code>
   * @since 3.4.0
   * @see StatementUtil#applyTransactionTimeout(Statement, Integer, Integer)
   */
  protected void applyTransactionTimeout(Statement statement) throws SQLException {
    StatementUtil.applyTransactionTimeout(statement, statement.getQueryTimeout(), transaction.getTimeout());
  }

  /**
   * 针对存储过程调用的处理，
   * <pre>
   *     其功能是：在一级缓存命中时，获取缓存中保持的输出类型参数，
   *             并设置到用户传入的实参（parameter)对象中
   * </pre
   * @param ms 对应的sql节点信息
   * @param key 缓存的 Key
   * @param parameter 参数
   * @param boundSql 对应的Sql语句
   */
  private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
    if (ms.getStatementType() == StatementType.CALLABLE) {
      final Object cachedParameter = localOutputParameterCache.getObject(key);
      if (cachedParameter != null && parameter != null) {
        final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
        final MetaObject metaParameter = configuration.newMetaObject(parameter);
        for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
          if (parameterMapping.getMode() != ParameterMode.IN) {
            final String parameterName = parameterMapping.getProperty();
            final Object cachedValue = metaCachedParameter.getValue(parameterName);
            metaParameter.setValue(parameterName, cachedValue);
          }
        }
      }
    }
  }

  /**
   * 查询数据库信息
   * <pre>
   *     调用 doQuery() 方完成数据库查询，并得到映射后的结果对象.
   *     doQuery() 方法是一个抽象方法，4个基本方法之一，由BaseExecutor 子类具体实现
   * </pre>
   */
  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    // 在缓存中添加占位符
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      // 执行查询
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      // 删除占位符
      localCache.removeObject(key);
    }
    // 将真正的结果对象添加到一级缓存中
    localCache.putObject(key, list);
    // 是否为存储过程调用
    if (ms.getStatementType() == StatementType.CALLABLE) {
      // 缓存输出类型的参数
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

  protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      return connection;
    }
  }

  @Override
  public void setExecutorWrapper(Executor wrapper) {
    this.wrapper = wrapper;
  }

  /**
   * 负责从 localCache 缓存中延迟加载结果对象
   */
  private static class DeferredLoad {

    /** 从外层对象对应的 MetaObject 对象 */
    private final MetaObject resultObject;
    /** 延迟加载的属性名称 */
    private final String property;
    /** 延迟加载的属性的类型 */
    private final Class<?> targetType;
    /** 延迟加载的结果对象在一级缓存中相应的 CacheKey 对象 */
    private final CacheKey key;
    /** 一级缓存，与 BaseExecutor.localCache 字段指向同一 PerpetualCache */
    private final PerpetualCache localCache;
    private final ObjectFactory objectFactory;
    /** 负责结果对象的类型转换 */
    private final ResultExtractor resultExtractor;

    // issue #781
    public DeferredLoad(MetaObject resultObject,
                        String property,
                        CacheKey key,
                        PerpetualCache localCache,
                        Configuration configuration,
                        Class<?> targetType) {
      this.resultObject = resultObject;
      this.property = property;
      this.key = key;
      this.localCache = localCache;
      this.objectFactory = configuration.getObjectFactory();
      this.resultExtractor = new ResultExtractor(configuration, objectFactory);
      this.targetType = targetType;
    }

    /**
     * 检测缓存项是否一级“完全加载”到缓存中
     * @return
     */
    public boolean canLoad() {
      return localCache.getObject(key) != null // 检测缓存中是否存在指定的结果对象
              && localCache.getObject(key) != EXECUTION_PLACEHOLDER; // 检测是否为占位符
    }

    /**
     * 从缓存中加载结果对象，并设置到外层对象的相应属性中
     */
    public void load() {
      @SuppressWarnings( "unchecked" )
      // we suppose we get back a List
      // 从缓存中查询指定的结果对象
      List<Object> list = (List<Object>) localCache.getObject(key);
      // 将缓存的结果对象转换成指定类型
      Object value = resultExtractor.extractObjectFromList(list, targetType);
      // 设置到外层对象的相应属性
      resultObject.setValue(property, value);
    }

  }

}
